package com.example.autumn.context;

import com.example.autumn.annotation.*;
import com.example.autumn.exeception.BeanCreationException;
import com.example.autumn.exeception.BeanDefinitionException;
import com.example.autumn.exeception.BeanNotOfRequiredTypeException;
import com.example.autumn.exeception.NoUniqueBeanDefinitionException;
import com.example.autumn.io.PropertyResolver;
import com.example.autumn.io.ResourceResolver;
import com.example.autumn.utils.ClassUtils;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author liuzhiyong
 * @date 2023/10/18
 * description: 应用上下文
 */
public class AnnotationConfigApplicationContext {

    protected final Logger logger = LoggerFactory.getLogger(getClass());
    protected final PropertyResolver propertyResolver;
    protected final Map<String, BeanDefinition> beans;

    /**
     * 构造方法
     *
     * @param configClass  配置类, 扫描此类所在的包以及子包的类, 如果`ComponentScan`注解指定了扫描的路径, 就扫描指定路径
     * @param propertyResolver 属性解析器, 解析配置文件
     * @author liuzhiyong
     * @date 2023/10/19
     */
    public AnnotationConfigApplicationContext(Class<?> configClass, PropertyResolver propertyResolver) {
        this.propertyResolver = propertyResolver;
        // 扫描所有Bean的Class类型
        final Set<String> beanClassNames = scanForClassNames(configClass);
        //创建Bean的定义信息
        this.beans = createBeanDefinitions(beanClassNames);
    }


    /**
     * 扫描响应的包路径, 并且返回类名
     *
     * @param configClass 配置类, 指定扫描的包路径, 类似于SpringBoot项目的启动类
     * @return {@link Set<String> }
     * @author liuzhiyong
     * @date 2023/10/19
     */
    protected Set<String> scanForClassNames(Class<?> configClass) {
        // 获取要扫描的package名称
        // 获取configClass上的ComponentScan注解, 递归获取
        ComponentScan scan = ClassUtils.findAnnotation(configClass, ComponentScan.class);
        // 如果scan为null 或者 没有指定扫描路径, 扫描configClass所在的包, 否则扫描scan中value指定的包路径
        final String[] scanPackages = scan == null || scan.value().length == 0 ? new String[] {configClass.getPackage().getName()} :scan.value();
        logger.atDebug().log("component scan in packages: {}", Arrays.toString(scanPackages));

        // 保存类名的Set集合
        Set<String> classNameSet= new HashSet<>();
        // 循环遍历指定扫描的包路径, 扫描其子包, 获取类名
        for (String pkg : scanPackages) {
            logger.atDebug().log("scan package: {}", pkg);
            // 创建指定包的资源解析解析器
            var rr = new ResourceResolver(pkg);
            // 扫描改包下的资源, 指定资源的映射函数==>获取class文件的名称
            // 例如: "org/example/Hello.class". 需要转成类名,例如: "org.example.Hello"
            List<String> classList = rr.scan(res -> {
                String name = res.name();
                if (name.endsWith(".class")) {
                    return name.substring(0, name.length() - 6).replace("/", ".").replace("\\", ".");
                }
                return null;
            });
            // debug级别, 打印类名
            if (logger.isDebugEnabled()) {
                classList.forEach((className) -> {
                    logger.debug("class found by component scan: {}", className);
                });
            }
            classNameSet.addAll(classList);
        }

        // 查找@Import(Xyz.class)
        Import importConfig = configClass.getAnnotation(Import.class);
        if (importConfig != null) {
            for (Class<?> importConfigClass : importConfig.value()) {
                String importClassName = importConfigClass.getName();
                if (classNameSet.contains(importClassName)) {
                    logger.warn("ignore import: " + importClassName + " for it is already been scanned.");
                } else {
                    logger.debug("class found by import: {}", importClassName);
                    classNameSet.add(importClassName);
                }
            }
        }
        return classNameSet;
    }

    /**
     * 根据扫描的ClassName创建BeanDefinition
     *
     * @param beanClassNames bean的名称集合
     * @return {@link Map<String,BeanDefinition> } bean定义信息 map
     * @author liuzhiyong
     * @date 2023/10/19
     */
    Map<String, BeanDefinition> createBeanDefinitions(Set<String> beanClassNames) {
        Map<String, BeanDefinition> defs = new HashMap<>();
        for (String className : beanClassNames) {
            // 获取Class
            Class<?> clazz = null;
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new BeanCreationException(e);
            }
            // 注解, 枚举, 接口,Record类型 ==> 不创建bean的定义信息
            if (clazz.isAnnotation() || clazz.isEnum() || clazz.isInterface() || clazz.isRecord()) {
                continue;
            }
            // 是否标注@Component ==> 标注了@Component注解创建BeanDefinition
            // 递归获取@Component注解
            Component component = ClassUtils.findAnnotation(clazz, Component.class);
            if (component != null) {
                logger.atDebug().log("found component: {}", clazz.getName());
                // 获取该类的修饰符类型 ==> 公共类, 抽象类, 内部类等
                int mod = clazz.getModifiers();
                // 判断是否为抽象类 ==> 抽象类不能加@Component注解, 不需要创建Bean Definition
                if (Modifier.isAbstract(mod)) {
                    throw new BeanDefinitionException("@Component class " + clazz.getName() + " must not be abstract.");
                }
                // 判断是否为内部类 ==> 内部类不能加@Component注解, 不需要创建Bean Definition
                if (Modifier.isPrivate(mod)) {
                    throw new BeanDefinitionException("@Component class " + clazz.getName() + " must not be private.");
                }
                // 获取递归获取@Component注解中value指定的名称, 或者包含@Component注解的value指定的名称, 如果没有执行, 会将类名转成小驼峰, 作为beanName
                String beanName = ClassUtils.getBeanName(clazz);
                var def = new BeanDefinition(beanName, clazz, getSuitableConstructor(clazz), getOrder(clazz), clazz.isAnnotationPresent(Primary.class),
                        null, null,
                        // init method:
                        ClassUtils.findAnnotationMethod(clazz, PostConstruct.class),
                        // destroy method:
                        ClassUtils.findAnnotationMethod(clazz, PreDestroy.class));
                addBeanDefinitions(defs, def);
                logger.atDebug().log("define bean: {}", def);

                // 判断当前类是否包含 @Configuration注解, 如果包含的话, 扫描其中有@Bean注解的函数, 创建BeanDefinition
                Configuration configuration = ClassUtils.findAnnotation(clazz, Configuration.class);
                if (configuration != null) {
                    scanFactoryMethods(beanName, clazz, defs);
                }
            }

        }
        return defs;
    }

    /**
     * 获取构造函数
     *
     * @param clazz
     * @return {@link Constructor<?> }
     * @author liuzhiyong
     * @date 2023/10/20
     */
    Constructor<?> getSuitableConstructor(Class<?> clazz) {
        // 获取class所有公共的构造函数, 包括父类的
        Constructor<?>[] cons = clazz.getConstructors();
        if (cons.length == 0) {
            // 获取class所有的构造函数(包括公共,默认,受保护,私有的, 但不包括从父类获得的构造函数)
            cons = clazz.getDeclaredConstructors();
            if (cons.length != 1) {
                throw new BeanDefinitionException("More than one constructor found in class " + clazz.getName() + ".");
            }
        }
        if (cons.length != 1) {
            throw new BeanDefinitionException("More than one public constructor found in class " + clazz.getName() + ".");
        }
        return cons[0];
    }

    /**
     * 获取排序
     * <code>
     * &#64;Order(100)
     * &#64;Component
     * public class Hello{}
     * </code>
     *
     * @param clazz class对象
     * @return {@link int }
     * @author liuzhiyong
     * @date 2023/10/20
     */
    int getOrder(Class<?> clazz) {
        Order order = clazz.getAnnotation(Order.class);
        return order == null ? Integer.MAX_VALUE : order.value();
    }

    /**
     * Get order by:
     *
     * <code>
     * &#64;Order(100)
     * &#64;Bean
     * Hello createHello() {
     *     return new Hello();
     * }
     * </code>
     */
    int getOrder(Method method) {
        Order order = method.getAnnotation(Order.class);
        return order == null ? Integer.MAX_VALUE : order.value();
    }

    /**
     * 添加BeanDefinition到Map
     *
     * @param defs map
     * @param def  bean定义信息
     * @author liuzhiyong
     * @date 2023/10/20
     */
    void addBeanDefinitions(Map<String, BeanDefinition> defs, BeanDefinition def) {
        // put()函数, key重复的话, 会返回旧的value值, 其他情况返回null, 不为null的话说明key重复
        if (defs.put(def.getName(), def) != null) {
            throw new BeanDefinitionException("Duplicate bean name: " + def.getName());
        }
    }

    /**
     *
     *
     * @param factoryBeanName 工厂对象名称
     * @param clazz
     * @param defs
     * @author liuzhiyong
     * @date 2023/10/20
     */
    void scanFactoryMethods(String factoryBeanName, Class<?> clazz, Map<String, BeanDefinition> defs) {
        // 获取所有方法
        for (Method method : clazz.getDeclaredMethods()) {
            // 获取@Bean注解, 判断方法上是否有@Bean注解
            Bean bean = method.getAnnotation(Bean.class);
            if (bean != null) {
                // 获取方法权限
                int mod = method.getModifiers();
                // 抽象方法 ==> 不能使用@Bean
                if (Modifier.isAbstract(mod)) {
                    throw new BeanDefinitionException("@Bean method " + clazz.getName() + "." + method.getName() + " must not be abstract.");
                }
                // final修饰的方法 ==> 不能使用@Bean
                if (Modifier.isFinal(mod)) {
                    throw new BeanDefinitionException("@Bean method " + clazz.getName() + "." + method.getName() + " must not be final.");
                }
                // 私有方法 ==> 不能使用@Bean
                if (Modifier.isPrivate(mod)) {
                    throw new BeanDefinitionException("@Bean method " + clazz.getName() + "." + method.getName() + " must not be private.");
                }
                // 获取返回值类型
                Class<?> beanClass = method.getReturnType();
                // 判断返回值是否为基础数据类型 ==> 返回值不允许为基础数据类型
                if (beanClass.isPrimitive()) {
                    throw new BeanDefinitionException("@Bean method " + clazz.getName() + "." + method.getName() + " must not return primitive type.");
                }
                // 判断返回值是都为void 或者 Void, ==> 不允许没有返回值
                if (beanClass == void.class || beanClass == Void.class) {
                    throw new BeanDefinitionException("@Bean method " + clazz.getName() + "." + method.getName() + " must not return void.");
                }
                //
                var def = new BeanDefinition(ClassUtils.getBeanName(method), beanClass, factoryBeanName, method, getOrder(method),
                        method.isAnnotationPresent(Primary.class),
                        // init method:
                        bean.initMethod().isEmpty() ? null : bean.initMethod(),
                        // destroy method:
                        bean.destroyMethod().isEmpty() ? null : bean.destroyMethod(),
                        // @PostConstruct / @PreDestroy method:
                        null, null);
                addBeanDefinitions(defs, def);
                logger.atDebug().log("define bean: {}", def);
            }
        }
    }


    /**
     * 根绝那么查询Bean Definition 如果Name不存在返回Null
     *
     * @param name 名称
     * @return {@link BeanDefinition }
     * @author liuzhiyong
     * @date 2023/10/20
     */
    @Nullable
    public BeanDefinition findBeanDefinition(String name) {
        return this.beans.get(name);
    }

    /**
     * 根据Name和Type查找BeanDefinition，如果Name不存在，返回null，如果Name存在，但Type不匹配，抛出异常。
     *
     * @param name bean名称
     * @param requiredType 类型
     * @return {@link BeanDefinition }
     * @author liuzhiyong
     * @date 2023/10/20
     */
    @Nullable
    public BeanDefinition findBeanDefinition(String name, Class<?> requiredType) {
        BeanDefinition def = findBeanDefinition(name);
        if (def == null) {
            return null;
        }
        if (!requiredType.isAssignableFrom(def.getBeanClass())) {
            throw new BeanNotOfRequiredTypeException(String.format("Autowire required type '%s' but bean '%s' has actual type '%s'.", requiredType.getName(),
                    name, def.getBeanClass().getName()));
        }
        return def;
    }

    /**
     * 根据Type查找若干个BeanDefinition，返回0个或多个。
     *
     * @param type
     * @return {@link List<BeanDefinition> }
     * @author liuzhiyong
     * @date 2023/10/20
     */
    public List<BeanDefinition> findBeanDefinitions(Class<?> type) {
        return this.beans.values().stream()
                // 过滤指定类型的Bean Definition
                .filter(def -> type.isAssignableFrom(def.getBeanClass()))
                // 排序
                .sorted()
                .collect(Collectors.toList());
    }

    /**
     * 根据Type查找某个BeanDefinition，如果不存在返回null，
     * 如果存在多个返回@Primary标注的一个，
     * 如果有多个@Primary标注，或没有@Primary标注但找到多个，均抛出NoUniqueBeanDefinitionException
     *
     * @param type
     * @return {@link BeanDefinition }
     * @author liuzhiyong
     * @date 2023/10/20
     */
    @Nullable
    public BeanDefinition findBeanDefinition(Class<?> type) {
        List<BeanDefinition> defs = findBeanDefinitions(type);
        if (defs.isEmpty()) {
            return null;
        }
        if (defs.size() == 1) {
            return defs.get(0);
        }
        // 超过一个Bean, 返回加了@Primary注解的BeanDefinition
        List<BeanDefinition> primaryDefs = defs.stream().filter(def -> def.isPrimary()).collect(Collectors.toList());
        if (primaryDefs.size() == 1) {
            return primaryDefs.get(0);
        }
        if (primaryDefs.isEmpty()) {
            throw new NoUniqueBeanDefinitionException(String.format("Multiple bean with type '%s' found, but no @Primary specified.", type.getName()));
        } else {
            throw new NoUniqueBeanDefinitionException(String.format("Multiple bean with type '%s' found, and multiple @Primary specified.", type.getName()));
        }
    }

}
